#ifndef JMETAMETHOD_H
#define JMETAMETHOD_H

#include <JXQCore/JConcurrent>
#include <JXQCore/JSingleton>
#include <JXQCore/JDebug>
#include <unordered_map>

namespace JXQ
{

#ifdef J_OS_WIN
#define J_INVOKE  __cdecl
#else
#define J_INVOKE
#endif

class JMeta;

template <typename...Args>
class JFunctionHash
{
	static JFunctionHash &instance();

	template <typename Return, typename Class>
	bool registerFunc(size_t id, J_INVOKE Return(Class::*method)(Args...));

	template<typename Return>
	bool registerFunc(size_t id, J_INVOKE Return(*func)(Args...));

	template <typename Return>
	std::function<J_INVOKE Return()>
	findClassMethod(size_t id, const void*, Args...);

	template<typename Return>
	std::function<J_INVOKE Return()>
	findGlobalFunc(size_t id, Args...);

	typedef std::unordered_map<size_t, AddressBitsWide> MethodHash;
	MethodHash m_funcHash;

	inline JFunctionHash() = default;
	friend class JMeta;
	J_DISABLE_COPY(JFunctionHash)
};

template <typename...Args>
inline JFunctionHash<Args...>&
JFunctionHash< Args...>::instance()
{
	static JFunctionHash<Args...> uniqueObject;
	return uniqueObject;
}

template <typename...Args>
template <typename Return, typename Class>
inline bool JFunctionHash<Args...>::
registerFunc(size_t id, J_INVOKE Return(Class::*method)(Args...))
{
	AddrTlr<Return(Class::*)(Args...)> at(method);
	return m_funcHash.emplace(id, at.addr).second;
}

template <typename...Args>
template <typename Return>
inline bool JFunctionHash<Args...>::
	registerFunc(size_t id, J_INVOKE Return(*func)(Args...))
{
	AddrTlr<Return(*)(Args...)> at(func);
	return m_funcHash.emplace(id, at.addr).second;
}

template <typename...Args>
template <typename Return>
std::function<J_INVOKE Return()>
JFunctionHash<Args...>::findClassMethod(size_t id, const void *obj, Args...args)
{
	auto it = m_funcHash.find(id);
	if( it == m_funcHash.end() )
		return nullptr;

	AddrTlr<J_INVOKE Return(*)(const void*, Args...)> at(it->second);
	return std::bind(at.ptr, obj, std::forward<Args>(args)...);
}

template <typename...Args>
template <typename Return>
std::function<J_INVOKE Return()>
JFunctionHash<Args...>::findGlobalFunc(size_t id, Args...args)
{
	auto it = m_funcHash.find(id);
	if( it == m_funcHash.end() )
		return nullptr;

	AddrTlr<J_INVOKE Return(*)(Args...)> at(it->second);
	return std::bind(at.ptr, std::forward<Args>(args)...);
}

/*-----------------------------------------------------------------------------------*/

class JXQLIB_EXPORT JFuncInfo
{
	template <typename Return, typename...Args>
	static size_t registerFunc(const std::string &name, J_INVOKE Return(*func)(Args...));

	template <typename Return, typename Class, typename...Args>
	static size_t registerFunc(const std::string &name, J_INVOKE Return(Class::*method)(Args...));

	static size_t find(size_t classTypeId, const std::string &name);
	static size_t find(const std::string &name);

	inline static std::string getInfo(size_t classTypeId, const std::string &name);
	inline static std::string getInfo(const std::string &name);
	static std::string getInfo(size_t id);

	inline JFuncInfo() {}
	inline ~JFuncInfo() {}

	typedef std::unordered_map<std::string, size_t> FuncIdHash;
	static FuncIdHash funcIdHash;

	typedef std::unordered_map<size_t, std::string> FuncInfoHash;
	static FuncInfoHash funcInfoHash;

	friend class JMeta;
	J_DISABLE_COPY(JFuncInfo)
};

template <typename Return, typename...Args>
inline size_t JFuncInfo::
	registerFunc(const std::string &name, J_INVOKE Return(*func)(Args...))
{
	const size_t id = typeid(func).hash_code();
	if( funcIdHash.emplace(name, id).second )
		funcInfoHash.emplace(id, ABI_CXA_DEMANGLE(typeid(func).name()));
	return id;
}

template <typename Return, typename Class, typename...Args>
inline size_t JFuncInfo::
	registerFunc(const std::string &name, J_INVOKE Return(Class::*method)(Args...))
{
	const size_t id = typeid(method).hash_code();

	char buf[(sizeof(size_t)<<3) + 1] = "";
	sprintf(buf, "%zu", typeid(Class).hash_code());

	if( funcIdHash.emplace(name + "_-_-_" + buf, id).second )
		funcInfoHash.emplace(id, ABI_CXA_DEMANGLE(typeid(method).name()));
	return id;
}

inline std::string JFuncInfo::getInfo(size_t classTypeId, const std::string &name)
{
	return getInfo(find(classTypeId, name));
}

inline std::string JFuncInfo::getInfo(const std::string &name)
{
	return getInfo(find(name));
}

} //namespace JXQ


#endif // JMETAMETHOD_H
